package cn.pilipa.common.config;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.io.IOException;
import java.util.Locale;
import javax.money.MonetaryException;
import javax.money.format.MonetaryAmountFormat;
import javax.money.format.MonetaryFormats;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.javamoney.moneta.Money;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Configures custom serialization and deserialization of {@link Money} instances.
 */
@Configuration
@ConditionalOnClass(SimpleModule.class)
public class JacksonConfig {

  @Bean
  public SimpleModule javaTimeModule() {
    return new JavaTimeModule();
  }

  @ConditionalOnClass(Money.class)
  @Configuration
  public static class MoneyCustomization {
    @Bean
    public SimpleModule moneyModule() {
      return new MoneyModule();
    }
  }

  @Slf4j
  public static class MoneyModule extends SimpleModule {

    public static final ThreadLocal<MonetaryAmountFormat> FORMAT = ThreadLocal.withInitial(() ->
        MonetaryFormats.getAmountFormat(Locale.CHINA)
    );

    public MoneyModule() {
      addSerializer(Money.class, new MoneySerializer());
      addValueInstantiator(Money.class, new MoneyInstantiator());
    }

    /**
     * money解析.
     *
     * @return 格式不对，返回null
     */
    public static Money parseMoney(String value) {
      if (StringUtils.isBlank(value)) {
        return null;
      }

      try {
        return Money.parse(value, FORMAT.get());
      } catch (MonetaryException e1) {
        try {
          return Money.parse("CNY " + value, FORMAT.get());
        } catch (Exception e2) {
          MoneyModule.logger.warn("Error to parse money value=" + value, e2);
          return null;
        }
      } catch (Exception e1) {
        MoneyModule.logger.warn("Error to parse money value=" + value, e1);
        return null;
      }

    }

    static class MoneySerializer extends ToStringSerializer {

      @Override
      public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider)
          throws IOException {
        jgen.writeString(FORMAT.get().format((Money) value));
      }
    }

    static class MoneyInstantiator extends ValueInstantiator {

      @Override
      public String getValueTypeDesc() {
        return Money.class.toString();
      }

      @Override
      public boolean canCreateFromString() {
        return true;
      }

      @Override
      public Object createFromString(DeserializationContext ctxt, String value) throws IOException {
        return parseMoney(value);
      }
    }
  }
}